Source code for qredtea.symmetries.abelianlinks

# This code is part of qredtea.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Abelian links and link weights for qtealeaves module.
"""

# pylint: disable=too-many-locals

import warnings
from copy import deepcopy

import numpy as np
from qtealeaves.tensors import QteaTensor

from qredtea.tooling import QRedTeaAbelianSymError, QRedTeaError, QRedTeaLinkError

from .couplingsectors import CouplingSectors
from .ibarrays import bmaskf, bmaskt, iany, iarray, ichoice, imax, isum, izeros
from .irreplistings import IrrepListing

__all__ = ["AbelianSymLink", "AbelianSymLinkWeight"]

RUN_SANITY_CHECKS = True






[docs] class AbelianSymLinkWeight: """ Diagonal matrix for symmetric tensors, i.e., a link weight. **Arguments** link : instance of :class:`AbelianSymLink` sectors : integer array, rank-2 link_weights : list of arrays with weights, same order as cs. base_tensor_cls : type Contains the base tensor class to perform default operations. """ # pylint: disable=too-many-arguments def __init__(self, link, sectors, link_weights, base_tensor_cls, allow_empty=False): self.link = link self.cs = CouplingSectors(sectors) self.link_weights = link_weights self.base_tensor_cls = base_tensor_cls if len(self) != len(link_weights): raise QRedTeaAbelianSymError( "Length of link weights does not match CS-length." ) if len(self) == 0 and (not allow_empty): raise QRedTeaAbelianSymError("Preventing creation of empty link weights.") def __len__(self): return self.cs.num_coupling_sectors def __itruediv__(self, scalar): """In-place division of link-weights with scalar (update).""" link_weights_div = [] for elem in self.link_weights: link_weights_div.append(elem / scalar) self.link_weights = link_weights_div def __pow__(self, power): link_weights_pow = [] for elem in self.link_weights: link_weights_pow.append(elem**power) return AbelianSymLinkWeight( self.link, self.cs.denormalized_sectors, link_weights_pow, self.base_tensor_cls, ) def __getitem__(self, key): idx_list = self.cs[key] if len(idx_list) > 1: raise QRedTeaAbelianSymError("Hashing all links should not lead to list.") return self.link_weights[idx_list[0]]
[docs] def generate_hashes(self): """Hashes are always generated for the only diagonal link.""" self.cs.generate_hashes([0])
[docs] def sum(self): """Sum over all the entries of link weights returning scalar.""" if len(self) == 0: return 0.0 if hasattr(self.link_weights[0], "sum"): value = self.link_weights[0].sum() else: # Bold guess we have tensorflow value = float(self.link_weights[0].cpu().numpy().sum()) for elem in self.link_weights[1:]: if hasattr(elem, "sum"): value += elem.sum() else: # Bold guess we have tensorflow value += elem.cpu().numpy().sum() return value
def _empty_tensor(self): """Generate an example tensor based on the backend.""" if self.base_tensor_cls == QteaTensor: if len(self) == 0: device = "cpu" tensor = self.base_tensor_cls([0]) dtype = tensor.dtype_real() elif isinstance(self.link_weights[0], np.ndarray): device = "cpu" dtype = self.link_weights[0].dtype else: device = "gpu" dtype = self.link_weights[0].dtype tensor = self.base_tensor_cls([0], dtype=dtype, device=device) else: tensor = self.base_tensor_cls([0]) if len(self) == 0: dtype = tensor.dtype_real() else: dtype = self.link_weights[0].dtype tensor = self.base_tensor_cls([0], dtype=dtype) return tensor
[docs] def flatten(self): """ Flatten the weights, i.e. concatenate the link_weight arrays across all the sectors. Returns ------- vec : array (np.ndarray | to.Tensor | ...) Array of the same type as the underlying :class:`_AbstractQteaBaseTensor.elem`. Sorted in decaying order. """ empty_tensor = self._empty_tensor() if len(self) == 0: # Example tensor is also empty tensor return empty_tensor.elem argsort, concatenate, flip = empty_tensor.get_attr( "argsort", "concatenate", "flip" ) value = self.link_weights[0] for elem in self.link_weights[1:]: value = concatenate((value, elem)) inds = argsort(value) value = value[inds] if empty_tensor.linear_algebra_library == "torch": value = flip(value, dims=(0,)) else: value = value[::-1] return value
[docs] def tolist(self): """ Flatten the weights, i.e. concatenate the link_weight arrays across all the sectors. Values will be sorted in decaying order. Returns ------- vec : list[float] Values sorted in decaying order. """ if len(self) == 0: return [] vec_flatten = self.flatten() empty_tensor = self._empty_tensor() vec_cpu = empty_tensor.get_of(vec_flatten) return [float(elem) for elem in vec_cpu]
[docs] def sanity_check(self): """Sanity check for link weights.""" if not RUN_SANITY_CHECKS: return assert self.cs.denormalized_sectors.shape[1] == 1 assert self.cs.denormalized_sectors.shape[0] == len(self.link_weights) for jj, cs in enumerate(self.cs.denormalized_sectors[:, 0]): deg_link = self.link.irrep_listing.degeneracies[cs] deg_vec = len(self.link_weights[jj]) if deg_link != deg_vec: msg = f"Degeneracy mismatch: {deg_link} (link) vs {deg_vec} (vector)" msg += f" at irreps {self.link.irrep_listing.irreps[cs, :]}." raise QRedTeaAbelianSymError(msg)